接下來的三篇,打算要來完成一個登入頁面,使用的技術如下:
以上這些框架以及技術,如果是有 Android 背景的話應該非常熟悉,網路上已經有非常多豐富的資源在介紹他們,也礙於篇幅的關係,就不會詳細解釋他們的用途跟概念了。
在這裡直接使用 Android Studio 提供的範本來進行修改:
打開專案後會發現裡面已經有基本的 MVVM 架構了,還有一些簡單的登入邏輯判斷,基本的架構圖如下:
打開專案結構,其中有兩個不同的資料型別,用來當作資料的回傳值,分別是 LoginResult
與 Result
。在這兩個資料型別中,一個是 Sum Type 、另一個是 Product Type。
sealed class Result<out T : Any> {
data class Success<out T : Any>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
override fun toString(): String {
return when (this) {
is Success<*> -> "Success[data=$data]"
is Error -> "Error[exception=$exception]"
}
}
}
data class LoginResult(
val success: LoggedInUserView? = null,
val error: Int? = null
)
根據之前的文章,我們偏好使用 Sum Type 而不是 Product Type ,更不用說 LoginResult
還有 Null 的情況了。所以接下來的任務就是將他修改成 Sum Type :
sealed class LoginState {
class Success(val displayName: String): LoginState()
class Failed(val errorMsg: Int): LoginState()
}
接下來看看 Result
用在什麼地方吧,我們發現到在 LoginRepository
與 LoginDataSource
都有他的蹤跡:
class LoginDataSource {
fun login(username: String, password: String): Result<LoggedInUser> {
try {
// TODO: handle loggedInUser authentication
val fakeUser = LoggedInUser(java.util.UUID.randomUUID().toString(), "Jane Doe")
return Result.Success(fakeUser)
} catch (e: Throwable) {
return Result.Error(IOException("Error logging in", e))
}
}
}
class LoginRepository(val dataSource: LoginDataSource) {
fun login(username: String, password: String): Result<LoggedInUser> {
// handle login
val result = dataSource.login(username, password)
if (result is Result.Success) {
setLoggedInUser(result.data)
}
return result
}
}
Result
在這邊是一個用來包裝登入狀態的 Sum Type,可能是成功或是失敗。但是我們又發現,在這個 Android studio 提供的範本中,使用的是同步的作法,但是一般來說,登入都是一個非同步的操作,所以接下來,引入一個第三方的非同步框架: RxJava
//build.gradle
dependencies {
...
implementation "io.reactivex.rxjava3:rxjava:3.0.6"
implementation "io.reactivex.rxjava3:rxkotlin:3.0.1"
}
在原來的回傳值之上,如果再加入非同步的概念的話,就會是 Single<Result<LoggedInUser>>
,是兩層的“容器”。但是一樣,之前的篇幅有提到過,RxJava 是內建有錯誤處理機制的,所以我們不需要有 Result
這層的容器,使用 Single
本身就可以表達全部的狀態了:
class LoginDataSource {
// 只有一組假的帳密
// User: yanbin, Password: 1234
fun login(username: String, password: String): Single<LoggedInUser> {
return Single.just(Pair(username, password))
// delay 3 秒來模擬非同步操作
.delay(3, TimeUnit.SECONDS)
.map { (username, password) ->
when {
username == "yanbin" && password == "123456" -> {
LoggedInUser(java.util.UUID.randomUUID().toString(), "Yanbin")
}
else -> throw IllegalArgumentException("User not exist")
}
}
}
}
class LoginRepository(val dataSource: LoginDataSource) {
fun login(username: String, password: String): Single<LoggedInUser> {
return dataSource.login(username, password)
// side effect operator
.doOnSuccess { setLoggedInUser(it) }
}
private fun setLoggedInUser(loggedInUser: LoggedInUser) {
this.user = loggedInUser
}
}